package com.ar4j;

import java.lang.reflect.Method;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import com.ar4j.handler.IMethodHandler;

/**
 * Main delegating invocation handler for all active record proxies. Simply uses the map in the given
 * active record meta data to invoke a specific IMethodHandler for the current active record class.
 */
public class DelegatingMethodInterceptor implements MethodInterceptor {
  private ActiveRecordMetadata metadata;
  private ActiveRecordContext context;
  
  public DelegatingMethodInterceptor(ActiveRecordMetadata metadata, ActiveRecordContext context) {
    this.metadata = metadata;
    this.context = context;
  }

  @Override
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

    // find a handler in the meta data and call it
    IMethodHandler handler = metadata.getMethodHandlerMap().get(method);
    if(handler == null) {
      throw new RuntimeException("Could not find handler for: " + method);
    }
    
    TransactionAttribute transactionAttribute = metadata.getMethodTransactionAttributeMap().get(method);
    if(transactionAttribute != null && transactionAttribute.getPropagationBehavior() != TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
      if(context.getPlatformTransactionManager() == null) {
        throw new IllegalStateException("Cannot execute transactional active record method without a platform transaction manager in the context, method: " + method);
      }
      
      TransactionTemplate template = new TransactionTemplate(context.getPlatformTransactionManager(), transactionAttribute);
      final IMethodHandler handlerForCallback = handler;
      final Object objForCallback = obj;
      final Method methodForCallback = method;
      final Object[] argsForCallback = args;
      final MethodProxy proxyForCallback = proxy;
      
      // execute in transaction
      Object out = template.execute(new TransactionCallback<Object>() {
        @Override
        public Object doInTransaction(TransactionStatus status) {
          try { return handlerForCallback.intercept(metadata, context, objForCallback, methodForCallback, argsForCallback, proxyForCallback); }
          catch(Throwable e) { throw new RuntimeException("An error occured executing handler within a transaction", e); }
        }
      });
      
      return out;
    } else {
      return handler.intercept(metadata, context, obj, method, args, proxy);
    }
  }

  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  }
}
